#include <stdio.h>
#include <stdlib.h>
#include "Structs.h"
#include "Pong3D.h"
#include <GL/glut.h>
#include <math.h>
#include "glfont.h"
#include <Windows.h>
#ifdef __WII__
#include "lib\wiiuse.h"
#endif
#pragma pack(1)


//INIT EXTERN VARS

GLfloat whiteSpecularLight[] = {1.0, 0.83, 0}; 
GLfloat paleAmbientLight[] = {0.5, 0.4, 0};
GLfloat blackAmbientLight[] = {0.1, 0.1, 0.1};
GLfloat whiteAmbientLight[] = {0.9, 0.9, 0.9};
GLfloat whiteDiffuseLight[] = {1.0, 1.0, 1.0};

GLfloat COLOR_WHITE[] = { 1.0f, 1.0f, 1.0f, 1.0f };
GLfloat COLOR_BLACK[] = { 0.0f, 0.0f, 0.0f, 1.0f };
GLfloat COLOR_RED[] = {1.0f, 0.0f, 0.0f, 1.0f};
GLfloat COLOR_BLUE[] = {0.0f, 0.0f, 1.0f, 1.0f};
GLfloat COLOR_GREEN[] = {0.0f, 1.0f, 0.0f, 1.0f};
GLfloat COLOR_YELLOW[] = {1.0f, 1.0f, 0.0f, 1.0f};
GLfloat COLOR_NEON_PINK[] = {1.0f, 0.43f, 0.78f, 1.0f};
GLfloat COLOR_GOLD[] = {1.0f, 0.843f, 0.0f, 1.0f};

GLfloat COLOR_BLACK_50[] = { 0.0f, 0.0f, 0.0f, 0.5f };
GLfloat COLOR_RED_50[] = {1.0f, 0.0f, 0.0f,  0.5f};
GLfloat COLOR_BLUE_50[] = {0.0f, 0.0f, 1.0f, 0.5f};
GLfloat COLOR_GREEN_50[] = {0.0f, 1.0f, 0.0f, 0.5f};
GLfloat COLOR_YELLOW_50[] = {1.0f, 1.0f, 0.0f, 0.5f};
GLfloat COLOR_NEON_PINK_50[] = {1.0f, 0.43f, 0.78f, 0.5f};

GLfloat _startPos[3] = {0,0,0};
GLfloat playerColors[2][4] = {{0.0f, 0.0f, 1.0f, 0.5f},
{0.0f, 1.0f, 0.0f, 0.5f}};
GLfloat paddlePosition[2][3] = {{0,0,NEAR_PLANE},
{0,0,FAR_PLANE}};
GLfloat paddleDeltaVec[3] = {0.2, 0.3, 0.1};
enum InputDevice _inputDevice[2] = {MOUSE, AI};


enum State _lastState = START;
enum State _state = START;
int _ballsInPlay = 0;

GLfloat centerPoint[] = {0,0,0};
GLfloat fov = 60.0f;

GLfloat _aspect = 1.0f;
GLfloat _debugRotate = 0.0f;
GLfloat paddleDimensions[4][3] = {{-paddleXRadius,-paddleYRadius, 1}, {-paddleXRadius,paddleYRadius,1}, {paddleXRadius,paddleYRadius,1}, {paddleXRadius,-paddleYRadius,1}};
GLint _trophyAngle = 0.0f;

//Lights
int redLight;
int blueLight;
int yellowLight;
int greenLight;
//END INIT EXTERN VARS



typedef struct
{

	GLbyte	identsize;              // Size of ID field that follows header (0)
	GLbyte	colorMapType;           // 0 = None, 1 = paletted
	GLbyte	imageType;              // 0 = none, 1 = indexed, 2 = rgb, 3 = grey, +8=rle
	unsigned short	colorMapStart;          // First colour map entry
	unsigned short	colorMapLength;         // Number of colors
	unsigned char 	colorMapBits;   // bits per palette entry
	unsigned short	xstart;                 // image x origin
	unsigned short	ystart;                 // image y origin
	unsigned short	width;                  // width in pixels
	unsigned short	height;                 // height in pixels
	GLbyte	bits;                   // bits per pixel (8 16, 24, 32)
	GLbyte	descriptor;             // image descriptor
} TGAHEADER;
#pragma pack(8)


void setPerspective(){
	GLfloat scene_width = WORLD_WIDTH - paddleXRadius*2;
	GLfloat scene_height = WORLD_HEIGHT - paddleYRadius*2;
	if (_state == IN_ROUND){
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
	}

	gluPerspective(fov,_aspect,1,500);

	if(__debug){
		printf("X: %f, Y: %f\n",_players[0].paddle.position[0]/scene_width, _players[0].paddle.position[1]/scene_height);
	}
	if(_state == IN_ROUND)
	{
		gluLookAt(_players[0].paddle.position[0]/scene_width*2, _players[0].paddle.position[1]/scene_height*2, 1,
			_players[0].paddle.position[0]/scene_width*2, _players[0].paddle.position[1]/scene_height*2,0,
			0,1,0);
	}
	glMatrixMode(GL_MODELVIEW);
}

void display(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

	glLoadIdentity();

	glPushMatrix();
	glRotatef(90,0,1,0);
	setPerspective();
	glPopMatrix();


	glTranslatef(0,0,-12);
	lighting();	

	drawSkybox();
	drawWalls();


	if (_state == START)
	{
		drawPickObjects();
	}
	else if(_state == IN_ROUND)
	{		
		updateAI();
		drawBalls();
		drawPaddles();
		checkCollisions();
		drawScore();
	} else if(_state == ROUND_OVER)
	{
		drawScore();
	}else if(_state == GAME_WIN){
		
		victoryScreen();
		drawScore();
	}

	glutSwapBuffers();
}


void victoryScreen(){
	int i;
	float scaleFactor = 5;
	float invScaleFactor = 0.2;
	glPushMatrix();

	glRotatef(_trophyAngle,0,1,0);
	if (_players[0].score > _players[1].score){
		PlaySound(TEXT("sounds\\Success.wav"),NULL, SND_FILENAME | SND_ASYNC);
		glScaled(scaleFactor,scaleFactor,scaleFactor);
		drawTrophy();
	}else{
		scaleFactor = 3;
		PlaySound(TEXT("sounds\\Error.wav"),NULL, SND_FILENAME | SND_ASYNC);
		invScaleFactor = 1.0 / scaleFactor;
		glScaled(scaleFactor,scaleFactor,scaleFactor);
		drawSkull();
	}

	_trophyAngle = (_trophyAngle + 3) % 360;			
	glScaled(invScaleFactor, invScaleFactor, invScaleFactor);//Undo scale to draw paddle

	for(i = 0; i < 2; i++){ 
		glPushMatrix(); //To apply rotations to both
		if ( i == 0){
			glTranslated(3,0,0);
		}else{
			glTranslated(-3,0,0);
		}
		glRotated(_trophyAngle, 0,1,0);
		drawVictoryPaddle();
		glPopMatrix();
	}
	glPopMatrix();
}

void updateAI(){
	GLfloat aiPos[3] = {0,0,0};
	int i;
	int ballToTrack = 0;

	//Find lowest in play ball and track that...
	for(i = 0; i < MAX_BALLS; i++){
		if (_balls[i].inPlay == true){
			ballToTrack = i;
			break;
		}
	}

	for (i = 0; i < NUM_PLAYERS;i++){
		if (_players[i].inputDevice == AI){
			getAIPaddlePosition(aiPos, _players[i].paddle.position[0], _players[i].paddle.position[1], _players[i].paddle.position[2],
				_balls[0].position[0], _balls[0].position[1], _balls[0].position[2], 0.12f, 0.05f);

			copyArray(_players[i].paddle.position, aiPos, 3);
			if (__debug){
				printf("Updating AI Position: %f, %f, %f\n", _players[i].paddle.position[0], _players[i].paddle.position[1], _players[i].paddle.position[2] );
			}
		}
	}
}

void timer(int value)
{
	glutPostRedisplay();
	glutTimerFunc(30, timer, 0);
}

float floatRand(float max)
{
	return max*2.0*rand()/(RAND_MAX+1.0) - max;
}

void keyboard(unsigned char key, int x, int y)
{
	int i;
	int maxInPlay = 0;
	switch (key) {
	case ' ':
		if (_state == START){ 
			changeState(IN_ROUND);
		}else if(_state == IN_ROUND){
			changeState(PAUSE);
		}else if(_state == PAUSE){
			changeState(IN_ROUND);
		}else if(_state == ROUND_OVER){
			changeState(IN_ROUND);
		}

		break;
	case 'B':case'b': //New Ball
		if (_state == IN_ROUND){
			for(i = 0; i < MAX_BALLS; i++)
				{
					if(_balls[i].inPlay == false)
					{
						_balls[i].position[0] = floatRand(2);
						_balls[i].position[1] = floatRand(2);
						_balls[i].position[2] = floatRand(2);

						_balls[i].vector[0] = floatRand(0.3);
						_balls[i].vector[1] = floatRand(0.3);
						_balls[i].vector[2] = 0.2f + floatRand(0.1);
						_balls[i].inPlay = true;
						_ballsInPlay++;
						break;
					}
				}
			}
		break;
	case 'V': case 'v':
		if(_ballsInPlay > 1){

			//Determine the highest ball which is in pla
			//Remove that one..
			for(i = 0; i < MAX_BALLS; i++){
				if (_balls[i].inPlay){
					maxInPlay = i;
				}
			}
			if (maxInPlay > 0){
				_balls[maxInPlay].inPlay = false;
				_ballsInPlay--;
				printf("Removing: %i\n", maxInPlay);
				printf("Num balls in play: %i\n", _ballsInPlay);
			}
		}
		break;

	case '1': 
		if(redLight == 1)
		{
			redLight = 0;
		}
		else
		{
			redLight = 1;
		}
		break;
	case '2': 
		if(blueLight == 1)
		{
			blueLight = 0;
		}
		else
		{
			blueLight = 1;
		}
		break;
	case '3':
		if(greenLight == 1)
		{
			greenLight = 0;
		}
		else
		{
			greenLight = 1;
		}
		break;
	case '4': 
		if(yellowLight == 1)
		{
			yellowLight = 0;
		}
		else
		{
			yellowLight = 1;
		}
		break;
	}

}

void speckeyboard(int key, int x, int y)
{
}

void changeState(enum State newState){
	int i;
	_lastState = _state;
	_state = newState;

	if ((_state == IN_ROUND && _lastState != PAUSE) 
		|| _state == ROUND_OVER){

			
			if(_state == IN_ROUND){
				_balls[0].inPlay = true;
				_ballsInPlay = 1;
			}
			else if(_state == ROUND_OVER)
			{
				_ballsInPlay = 0;
				printf("ROUND OVER");
				for(i = 0; i < MAX_BALLS; i++){
					copyArray(_balls[i].position, centerPoint,3);
					_balls[i].angleX = _balls[i].angleY = _balls[i].angleZ = 0.0f;
					_balls[i].inPlay = false;
					_balls[i].vector[0] = floatRand(0.3);
					_balls[i].vector[1] = floatRand(0.3);
					_balls[i].vector[2] = 0.2f + floatRand(0.1);
					_balls[i].position[0] = 0.0f;
					_balls[i].position[1] = 0.0f;
					_balls[i].position[2] = 0.0f;
			}
			}
	}else if(_state == GAME_WIN){
	}


}

void mouse(int button, int state, int x, int y)
{
	GLuint returned;
	int i;
	//Left button pressed down
	if (_state == START) //Allow picking at begining of ame
	{
		if(button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
		{
			returned = pickObject(x, y);
			if (returned != -1){ // If a valid texture is chosen, set it!
				for (i=0; i < MAX_BALLS; i++){
					if (__debug)
						printf("Set texture %i on object %i",returned, i);

					_balls[i].textureName = returned;
				}

				changeState(IN_ROUND);
				_balls[0].inPlay = true;
			}
		}
	}
}

void passiveMotion(int x, int y)
{
	int i = 0;
	float xx, yy;
	GLfloat scene_width = WORLD_WIDTH - paddleXRadius*2;
	GLfloat scene_height = WORLD_HEIGHT - paddleYRadius*2;
	for(;i < NUM_PLAYERS; i++){
		if (_players[i].inputDevice == MOUSE || _players[i].inputDevice == WII){

			if (__debug){
				printf("scene_width: %f \n", scene_width);
				printf("scene_height: %f \n", scene_height);
				printf("x: %i  y: %i\n", x, y);
				printf("width: %i\n", width);
				printf("height: %i\n", height);
			}


			xx = ((float)x/(float)width) * scene_width - scene_width/2;
			yy = ((float)(height - y)/(float)height) * scene_height - scene_height/2;


			if (xx > scene_width/2){
				xx = scene_width/2;
			}else if(xx < -1*scene_width/2){
				xx = -1*scene_width/2;
			}

			if (yy > scene_height/2){
				yy = scene_height/2;
			}else if(yy < -1*scene_height/2){
				yy = -1*scene_height/2;
			}

			_players[i].paddle.position[0] = xx;
			_players[i].paddle.position[1] = yy;
		}
	}


}

void drawPaddles(void){
	int i = 0;
	GLfloat origin[] = {0,0,0};
	GLfloat spotColor[] = {1, 1, 1};

	for(i=NUM_PLAYERS-1; i >= 0; i--){
		glTranslatef(_players[i].paddle.position[0], _players[i].paddle.position[1], _players[i].paddle.position[2]);
		if(_players[i].paddle.position[2] == FAR_PLANE)
			glRotatef(180.0f,0,1,0);
		glColor4fv(_players[i].paddle.color);

		glBegin(GL_QUADS);
		glVertex3fv(paddleDimensions[0]);
		glVertex3fv(paddleDimensions[1]);
		glVertex3fv(paddleDimensions[2]);
		glVertex3fv(paddleDimensions[3]);
		glEnd();

		if(_players[i].paddle.position[2] == FAR_PLANE)
			glRotatef(-180.0f,0,1,0);

		glTranslatef(-1*_players[i].paddle.position[0], -1*_players[i].paddle.position[1], -1*_players[i].paddle.position[2]);
	}
}

void lighting (void) {

	GLfloat global_ambient[] = {0.1, 0.1, 0.1, 1.0};

	float light0_end_position[] =
	{ -14.0f, 10.0f, -30.0f, 0.0f };

	float light1_end_position[] =
	{ 14.0f, 10.0f, 30.0f, 0.0f };

	float light0_game_position[] =
	{ -14.0f, 0.0f, -10.0f, 0.0f };

	float light1_game_position[] =
	{ 14.0f, 0.0f, -10.0f, 0.0f };

	float light2_game_position[] =
	{ 0.0f, 14.0f, -10.0f, 0.0f };

	float light3_game_position[] =
	{ 0.0f, -14.0f, -10.0f, 0.0f };

	glEnable(GL_DEPTH_TEST);

	glEnable (GL_BLEND);
	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	glEnable(GL_LIGHTING);


	if(_state == GAME_WIN)
	{
		glEnable(GL_LIGHT0);
		glLightfv(GL_LIGHT0, GL_SPECULAR, paleAmbientLight);
		glLightfv(GL_LIGHT0, GL_AMBIENT, whiteAmbientLight);
		glLightfv(GL_LIGHT0, GL_DIFFUSE, paleAmbientLight);
		glLightfv(GL_LIGHT0, GL_POSITION, light0_end_position);

		glEnable(GL_LIGHT1);
		glLightfv(GL_LIGHT1, GL_SPECULAR, paleAmbientLight);
		glLightfv(GL_LIGHT1, GL_AMBIENT, whiteAmbientLight);
		glLightfv(GL_LIGHT1, GL_DIFFUSE, paleAmbientLight);
		glLightfv(GL_LIGHT1, GL_POSITION, light1_end_position);

		glDisable(GL_LIGHT2);
		glDisable(GL_LIGHT3);
	}
	else
	{
		if(redLight == 1)
		{
			glEnable(GL_LIGHT0);
			glLightfv(GL_LIGHT0, GL_SPECULAR, COLOR_RED);
			glLightfv(GL_LIGHT0, GL_AMBIENT, blackAmbientLight);
			glLightfv(GL_LIGHT0, GL_DIFFUSE, COLOR_RED);
			glLightfv(GL_LIGHT0, GL_POSITION, light0_game_position);
		}
		else
		{
			glDisable(GL_LIGHT0);
		}

		if(blueLight == 1)
		{
			glEnable(GL_LIGHT1);
			glLightfv(GL_LIGHT1, GL_SPECULAR, COLOR_BLUE);
			glLightfv(GL_LIGHT1, GL_AMBIENT, blackAmbientLight);
			glLightfv(GL_LIGHT1, GL_DIFFUSE, COLOR_BLUE);
			glLightfv(GL_LIGHT1, GL_POSITION, light1_game_position);
		}
		else
		{
			glDisable(GL_LIGHT1);
		}

		if(yellowLight == 1)
		{
			glEnable(GL_LIGHT2);
			glLightfv(GL_LIGHT2, GL_SPECULAR, COLOR_YELLOW);
			glLightfv(GL_LIGHT2, GL_AMBIENT, blackAmbientLight);
			glLightfv(GL_LIGHT2, GL_DIFFUSE, COLOR_YELLOW);
			glLightfv(GL_LIGHT2, GL_POSITION, light2_game_position);
		}
		else
		{
			glDisable(GL_LIGHT2);
		}

		if(greenLight == 1)
		{
			glEnable(GL_LIGHT3);
			glLightfv(GL_LIGHT3, GL_SPECULAR, COLOR_GREEN);
			glLightfv(GL_LIGHT3, GL_AMBIENT, blackAmbientLight);
			glLightfv(GL_LIGHT3, GL_DIFFUSE, COLOR_GREEN);
			glLightfv(GL_LIGHT3, GL_POSITION, light3_game_position);
		}
		else
		{
			glDisable(GL_LIGHT3);
		}

	}

	glEnable(GL_COLOR_MATERIAL);
	glColorMaterial(GL_FRONT,GL_AMBIENT_AND_DIFFUSE);
}

////////////////////////////////////////////////////////////////////
// Allocate memory and load targa bits. Returns pointer to new buffer,
// height, and width of texture, and the OpenGL format of data.
// Call free() on buffer when finished!
// This only works on pretty vanilla targas... 8, 24, or 32 bit color
// only, no palettes, no RLE encoding.
GLbyte *gltLoadTGA(const char *szFileName, GLint *iWidth, GLint *iHeight, GLint *iComponents, GLenum *eFormat)
{
	FILE *pFile;			// File pointer
	TGAHEADER tgaHeader;		// TGA file header
	unsigned long lImageSize;		// Size in bytes of image
	short sDepth;			// Pixel depth;
	GLbyte	*pBits = NULL;          // Pointer to bits

	// Default/Failed values
	*iWidth = 0;
	*iHeight = 0;
	*eFormat = GL_BGR_EXT;
	*iComponents = GL_RGB8;

	// Attempt to open the fil
	pFile = fopen(szFileName, "rb");
	if(pFile == NULL)
		return NULL;

	// Read in header (binary)
	fread(&tgaHeader, 18/* sizeof(TGAHEADER)*/, 1, pFile);

	// Get width, height, and depth of texture
	*iWidth = tgaHeader.width;
	*iHeight = tgaHeader.height;
	sDepth = tgaHeader.bits / 8;

	// Put some validity checks here. Very simply, I only understand
	// or care about 8, 24, or 32 bit targa's.
	if(tgaHeader.bits != 8 && tgaHeader.bits != 24 && tgaHeader.bits != 32)
		return NULL;

	// Calculate size of image buffer
	lImageSize = tgaHeader.width * tgaHeader.height * sDepth;

	// Allocate memory and check for success
	pBits = (GLbyte*)(malloc(lImageSize * sizeof(GLbyte)));
	if(pBits == NULL)
		return NULL;

	// Read in the bits
	// Check for read error. This should catch RLE or other 
	// weird formats that I don't want to recognize
	if(fread(pBits, lImageSize, 1, pFile) != 1)
	{
		free(pBits);
		return NULL;
	}

	// Set OpenGL format expected
	switch(sDepth)
	{
	case 3:     // Most likely case
		*eFormat = GL_BGR_EXT;
		*iComponents = GL_RGB8;
		break;
	case 4:
		*eFormat = GL_BGRA_EXT;
		*iComponents = GL_RGBA8;
		break;
	case 1:
		*eFormat = GL_LUMINANCE;
		*iComponents = GL_LUMINANCE8;
		break;
	};


	// Done with File
	fclose(pFile);

	// Return pointer to image data
	return pBits;
}


void init(){
	int i ;

	redLight = 1;
	blueLight = 1;
	greenLight = 1;
	yellowLight = 1;

	for (i = 0; i < NUM_PLAYERS; i++){
		copyArray(_paddles[i].color, playerColors[i], 4);
		copyArray(_paddles[i].position, paddlePosition[i], 3);
		_paddles[i].size = 1;
		_players[i].paddle = _paddles[i];
		_players[i].inputDevice = _inputDevice[i];
		_players[i].score = 0;
	}
#ifdef __WII__
	if(connect_wii())
	{
		for (i = 0; i < NUM_PLAYERS; i++)
		{
			if(_players[i].inputDevice == MOUSE){
				_players[i].inputDevice = WII;
				printf("Player %i set to WII",i);
			}
		}
	}
	else
	{
		glutPassiveMotionFunc(passiveMotion);
	}
#else
	glutPassiveMotionFunc(passiveMotion);
#endif

	//Set game parameters
	loadSkyBox();
	initballs();
	defineWalls();

	_state = START;
}

void reshape(int w, int h)
{
	// Prevent a divide by zero
	if(h == 0)
		h = 1;
	if(w == 0)
		w = 1;
	height = h;
	width = w;
	// Set Viewport to window dimensions
	glViewport(0, 0, w, h);

	// Calculate aspect ratio of the window
	fAspect = (GLfloat)w/(GLfloat)h;

	// Set the perspective coordinate system
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	// Field of view of 45 degrees, near and far planes 1.0 and 425
	setPerspective();

	// Modelview matrix reset
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}


void wii_update(int value)
{
#ifdef __WII__
	poll_wii();
	glutTimerFunc(5, wii_update, 0);
#endif
}


int main(int argc, char** argv)
{
	// normal initialisation
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
	glutInitWindowSize(800,500);
	_aspect = 800.0f/500.0f;
	glutInitWindowPosition(100,100);

	win = glutCreateWindow("Pong3d");

	//THROW CALLBACKS HERE
	glutDisplayFunc(display);
	glutTimerFunc(30, timer, 0);

	glutKeyboardFunc(keyboard);
	glutSpecialFunc(speckeyboard);
	glutMouseFunc(mouse);
	glutReshapeFunc(reshape);
	// enter the main loop

	init();

#ifdef __WII__
	glutTimerFunc(5, wii_update, 0);
#endif

	glutMainLoop();
	return(0);
}